//==============================================================================
//
//  OvenMediaEngine
//
//  Created by Hyunjun Jang
//  Copyright (c) 2025 AirenSoft. All rights reserved.
//
//==============================================================================
#include "./amf_property.h"

#include <string.h>

#include "../rtmp_private.h"
#include "./amf_object_array.h"
#include "./amf_strict_array.h"

namespace modules::rtmp
{
	AmfProperty::AmfProperty()
		: AmfProperty(AmfTypeMarker::Null)
	{
	}

	AmfProperty::AmfProperty(AmfTypeMarker type)
		: AmfPropertyBase(type)
	{
	}

	AmfProperty::AmfProperty(double number)
		: AmfPropertyBase(AmfTypeMarker::Number),
		  _number(number)
	{
	}

	AmfProperty::AmfProperty(uint32_t number)
		: AmfProperty(static_cast<double>(number))
	{
	}

	AmfProperty::AmfProperty(bool boolean)
		: AmfPropertyBase(AmfTypeMarker::Boolean),
		  _boolean(boolean)
	{
	}

	AmfProperty::AmfProperty(const char *string)
		: AmfPropertyBase(AmfTypeMarker::String),
		  _string(string)
	{
	}

	AmfProperty::AmfProperty(const AmfObject &object)
		: AmfPropertyBase(AmfTypeMarker::Object),
		  _object(new AmfObject(object))
	{
	}

	AmfProperty::AmfProperty(const AmfEcmaArray &array)
		: AmfPropertyBase(AmfTypeMarker::EcmaArray),
		  _ecma_array(new AmfEcmaArray(array))
	{
	}

	AmfProperty::AmfProperty(const AmfStrictArray &array)
		: AmfPropertyBase(AmfTypeMarker::StrictArray),
		  _strict_array(new AmfStrictArray(array))
	{
	}

	AmfProperty::AmfProperty(const AmfProperty &other)
		: AmfPropertyBase(other._amf_data_type),
		  _number(other._number),
		  _boolean(other._boolean),
		  _string(other._string),
		  _object((other._object != nullptr) ? new AmfObject(*other._object) : nullptr),
		  _ecma_array((other._ecma_array != nullptr) ? new AmfEcmaArray(*(other._ecma_array)) : nullptr),
		  _strict_array((other._strict_array != nullptr) ? new AmfStrictArray(*(other._strict_array)) : nullptr)

	{
	}

	AmfProperty::AmfProperty(AmfProperty &&other) noexcept
		: AmfPropertyBase(std::move(other))
	{
		std::swap(_number, other._number);
		std::swap(_boolean, other._boolean);
		std::swap(_string, other._string);
		std::swap(_object, other._object);
		std::swap(_ecma_array, other._ecma_array);
		std::swap(_strict_array, other._strict_array);
	}

	AmfProperty &AmfProperty::operator=(const AmfProperty &other)
	{
		if (this != &other)
		{
			Release();

			_amf_data_type = other._amf_data_type;
			_number = other._number;
			_boolean = other._boolean;

			_string = other._string;
			_object = (other._object != nullptr) ? new AmfObject(*other._object) : nullptr;
			_ecma_array = (other._ecma_array != nullptr) ? new AmfEcmaArray(*other._ecma_array) : nullptr;
			_strict_array = (other._strict_array != nullptr) ? new AmfStrictArray(*other._strict_array) : nullptr;
		}

		return *this;
	}

	AmfProperty &AmfProperty::operator=(AmfProperty &&other) noexcept
	{
		if (this != &other)
		{
			std::swap(_amf_data_type, other._amf_data_type);
			std::swap(_number, other._number);
			std::swap(_boolean, other._boolean);
			std::swap(_string, other._string);
			std::swap(_object, other._object);
			std::swap(_ecma_array, other._ecma_array);
			std::swap(_strict_array, other._strict_array);
		}

		return *this;
	}

	AmfProperty::~AmfProperty()
	{
		Release();
	}

	bool AmfProperty::EncodeNumber(ov::ByteStream &byte_stream) const
	{
		// DOUBLE
		uint64_t number;
		::memcpy(&number, &_number, 8);

		return byte_stream.WriteBE64(number);
	}

	bool AmfProperty::EncodeBoolean(ov::ByteStream &byte_stream) const
	{
		// U8 (0 is false, <> 0 is true)
		return byte_stream.Write8(_boolean ? 1 : 0);
	}

	bool AmfProperty::EncodeString(ov::ByteStream &byte_stream) const
	{
		// UTF8-char = UTF8-1 | UTF8-2 | UTF8-3 | UTF8-4
		// UTF8-1 = %x00-7F
		// UTF8-2 = %xC2-DF UTF8-tail
		// UTF8-3 = %xE0 %xA0-BF UTF8-tail | %xE1-EC 2(UTF8-tail) |
		//			%xED %x80-9F UTF8-tail | %xEE-EF 2(UTF8-tail)
		// UTF8-4 = %xF0 %x90-BF 2(UTF8-tail) | %xF1-F3 3(UTF8-tail) |
		//			%xF4 %x80-8F 2(UTF8-tail)
		// UTF8-tail = %x80-BF

		// UTF-8 = U16 *(UTF8-char)
		// UTF-8-long = U32 *(UTF8-char)
		// UTF-8-empty = U16

		return
			// U16
			byte_stream.WriteBE16(static_cast<uint16_t>(_string.GetLength())) &&
			// UTF8-char
			byte_stream.Write(_string.CStr(), _string.GetLength());
	}

	bool AmfProperty::DecodeNumber(ov::ByteStream &byte_stream)
	{
		if (byte_stream.IsRemained(8) == false)
		{
			logtd("Failed to decode number - not enough data (%zu bytes remained)", byte_stream.Remained());
			return false;
		}

		const auto data = byte_stream.ReadBE64();
		::memcpy(&_number, &data, 8);

		return true;
	}

	bool AmfProperty::DecodeBoolean(ov::ByteStream &byte_stream)
	{
		if (byte_stream.IsRemained(1) == false)
		{
			logtd("Failed to decode boolean - not enough data (%zu bytes remained)", byte_stream.Remained());
			return false;
		}

		_boolean = (byte_stream.Read8() != 0) ? true : false;

		return true;
	}

	bool AmfProperty::DecodeString(ov::ByteStream &byte_stream)
	{
		if (byte_stream.IsRemained(2) == false)
		{
			logtd("Failed to decode string - not enough data (%zu bytes remained)", byte_stream.Remained());
			return false;
		}

		auto length = byte_stream.ReadBE16();

		if (length == 0)
		{
			logtd("Empty string found");
			return true;
		}

		if (byte_stream.IsRemained(length) == false)
		{
			logtd("Invalid string length: %u (%zu bytes remained)", length, byte_stream.Remained());
			return false;
		}

		_string.SetLength(length);
		byte_stream.Read(_string.GetBuffer(), length);

		return true;
	}

	void AmfProperty::Release()
	{
		OV_SAFE_DELETE(_ecma_array);
		OV_SAFE_DELETE(_strict_array);
		OV_SAFE_DELETE(_object);
	}

	bool AmfProperty::Encode(ov::ByteStream &byte_stream, bool encode_marker) const
	{
		if (encode_marker && EncodeMarker(byte_stream) == false)
		{
			logtd("Failed to encode marker");
			return false;
		}

		auto succeeded = false;

		switch (_amf_data_type)
		{
			case AmfTypeMarker::Number:
				succeeded = EncodeNumber(byte_stream);
				break;

			case AmfTypeMarker::Boolean:
				succeeded = EncodeBoolean(byte_stream);
				break;

			case AmfTypeMarker::String:
				succeeded = (_string != nullptr) ? EncodeString(byte_stream) : false;
				break;

			case AmfTypeMarker::Object:
				succeeded = (_object != nullptr) ? _object->Encode(byte_stream, false) : 0;
				break;

			case AmfTypeMarker::Null:
				[[fallthrough]];
			case AmfTypeMarker::Undefined:
				succeeded = true;
				break;

			case AmfTypeMarker::EcmaArray:
				succeeded = (_ecma_array != nullptr) ? _ecma_array->Encode(byte_stream, false) : 0;
				break;

			case AmfTypeMarker::StrictArray:
				succeeded = (_strict_array != nullptr) ? _strict_array->Encode(byte_stream, false) : false;
				break;

			default:
				break;
		}

		return succeeded;
	}

	bool AmfProperty::Decode(ov::ByteStream &byte_stream, bool decode_marker)
	{
		if (decode_marker)
		{
			if (byte_stream.IsRemained(1) == false)
			{
				OV_ASSERT2(byte_stream.IsRemained(1));
				return false;
			}

			if (DecodeMarker(byte_stream, false) == false)
			{
				logtd("Failed to decode marker");
				return false;
			}
		}

		switch (_amf_data_type)
		{
			case AmfTypeMarker::Number:
				return DecodeNumber(byte_stream);

			case AmfTypeMarker::Boolean:
				return DecodeBoolean(byte_stream);

			case AmfTypeMarker::String:
				return DecodeString(byte_stream);

			case AmfTypeMarker::Object:
				OV_SAFE_DELETE(_object);
				_object = new AmfObject();
				if (_object->Decode(byte_stream, false))
				{
					_amf_data_type = AmfTypeMarker::Object;
					return true;
				}

				OV_SAFE_DELETE(_object);
				break;

			case AmfTypeMarker::Null:
				[[fallthrough]];
			case AmfTypeMarker::Undefined:
				return true;

			case AmfTypeMarker::EcmaArray:
				OV_SAFE_DELETE(_ecma_array);
				_ecma_array = new AmfEcmaArray();
				if (_ecma_array->Decode(byte_stream, false))
				{
					return true;
				}

				OV_SAFE_DELETE(_ecma_array);
				break;

			case AmfTypeMarker::StrictArray:
				OV_SAFE_DELETE(_strict_array);
				_strict_array = new AmfStrictArray();
				if (_strict_array->Decode(byte_stream, false))
				{
					_amf_data_type = AmfTypeMarker::StrictArray;
					return true;
				}

				OV_SAFE_DELETE(_strict_array);
				break;

			default:
				logtw("Unknown AMF type: %d", _amf_data_type);
				_amf_data_type = AmfTypeMarker::Null;
				break;
		}

		return false;
	}

	void AmfProperty::ToString(ov::String &description, size_t indent) const
	{
		bool show_type = true;

		switch (GetType())
		{
			case AmfTypeMarker::Number:
				description.AppendFormat("%.1f", GetNumber());
				break;

			case AmfTypeMarker::Boolean:
				description.AppendFormat("%s", GetBoolean() ? "true" : "false");
				break;

			case AmfTypeMarker::String:
				if (_string != nullptr)
				{
					description.AppendFormat("\"%s\"", GetString().CStr());
				}
				else
				{
					description.Append("<null>");
				}
				break;

			case AmfTypeMarker::Object:
				show_type = false;

				if (_object != nullptr)
				{
					_object->ToString(description, indent);
				}
				else
				{
					description.Append("<null-object>");
				}

				break;

			case AmfTypeMarker::Null:
				[[fallthrough]];
			case AmfTypeMarker::Undefined:
				break;

			case AmfTypeMarker::EcmaArray:
				show_type = false;

				if (_ecma_array != nullptr)
				{
					_ecma_array->ToString(description, indent);
				}
				else
				{
					description.Append("<null-ecma-array>");
				}
				break;

			case AmfTypeMarker::StrictArray:
				show_type = false;

				if (_strict_array != nullptr)
				{
					_strict_array->ToString(description, indent);
				}
				else
				{
					description.Append("<null-strict-array>");
				}
				break;

			case AmfTypeMarker::MovieClip:
				[[fallthrough]];
			case AmfTypeMarker::Reference:
				[[fallthrough]];
			case AmfTypeMarker::ObjectEnd:
				[[fallthrough]];
			case AmfTypeMarker::Date:
				[[fallthrough]];
			case AmfTypeMarker::LongString:
				[[fallthrough]];
			case AmfTypeMarker::Unsupported:
				[[fallthrough]];
			case AmfTypeMarker::Recordset:
				[[fallthrough]];
			case AmfTypeMarker::Xml:
				[[fallthrough]];
			case AmfTypeMarker::TypedObject:
				logte("Not supported AMF property type: %s", EnumToString(GetType()));
				OV_ASSERT(false, "Not supported type: ", GetType());
				break;
		}

		if (show_type)
		{
			description.AppendFormat("  (%s)", EnumToString(GetType()));
		}
	}

	ov::String AmfProperty::ToString(size_t indent) const
	{
		ov::String description;
		ToString(description, indent);
		return description;
	}
}  // namespace modules::rtmp
